import {
  CommandDialog,
  Icon,
  ParameterObjectType,
  pickedVertexData,
  PickLine,
} from '@amcax/base';
import { ctx2, previewer } from '@app-cad/module';
import { useAsyncEffect } from 'ahooks';
import { useState, useMemo, useEffect, useRef } from 'react';
import { pickedLineData, pickedFaceData } from '@amcax/base';
import { useCommand } from '@app-cad/common/hooks/useCommand';
import { CurvedBaseCommand, CurvedCmdDialogParams } from '@app-cad/types';
import { useTools } from '@app-cad/common/hooks/useTools';
import { getFaceNormal, getIdOfPickedObj } from '@app-cad/common/utils/render';
import { PickInputValueType } from '@app-cad/common/components/pick-input/3d';
import { CommandFormWrapper } from '@app-cad/common/components/command-form';
import { PickInputForFocus } from '@app-cad/common/components/command-form/auto-focus/pick-input';
import { Checkbox, Select } from 'antd';
import { InputWithFormat } from '@app-base/common/components/command-form/InputWithFormat';
import { CommandFormFooter } from '@app-cad/common/components/command-form/footer';
import { i18n } from '@app-nextmesh/common/utils';
import { Vector3 } from 'three-legacy';

type Params = {
  mode: LimitMode;
  profile: Array<string>;
  vector?: string;
  reverse?: boolean;
  distance?: number;
  element?: string;
  mode2: LimitMode;
  reverse2?: boolean;
  distance2?: number;
  element2?: string;
};

type LimitMode = 'distance' | 'toElement';
type SingleLimit = {
  mode: LimitMode;
  reverse?: boolean;
  element?: pickedFaceData | pickedVertexData;
  distance?: number;
  offset?: number;
};

export function CmdExtrudeSurface({
  title,
  onClose,
  props,
}: CurvedCmdDialogParams) {
  const { picker } = useTools();

  const [pickedProfile, setPickedProfile] = useState<pickedLineData[]>([]);
  const [isMirrorRange, setIsMirrorRange] = useState(false);

  const [pickedVector, setPickedVector] = useState<
    pickedLineData | pickedFaceData
  >();

  const [firstLimit, setFirstLimit] = useState<SingleLimit>({
    mode: 'distance',
    distance: 20,
    offset: 0,
  });

  const [secondLimit, setSecondLimit] = useState<SingleLimit>({
    mode: 'distance',
    distance: 0,
    offset: 0,
  });

  const params: Params = useMemo(() => {
    const finalSecondLimit = isMirrorRange ? { ...firstLimit } : secondLimit;

    return {
      profile: pickedProfile.map(getIdOfPickedObj),
      vector: getIdOfPickedObj(pickedVector),
      mode: firstLimit.mode,
      reverse: firstLimit.reverse,
      distance:
        firstLimit.mode === 'distance'
          ? firstLimit.distance
          : firstLimit.offset,
      element: getIdOfPickedObj(firstLimit.element),
      mode2: finalSecondLimit.mode,
      reverse2: finalSecondLimit.reverse,
      distance2:
        finalSecondLimit.mode === 'distance'
          ? finalSecondLimit.distance
          : finalSecondLimit.offset,
      element2: getIdOfPickedObj(finalSecondLimit.element),
    } as Params;
  }, [pickedProfile, pickedVector, firstLimit, secondLimit, isMirrorRange]);

  useEffect(() => {
    execute(true);
  }, [firstLimit.reverse, secondLimit.reverse]);

  useEffect(() => {
    if (isMirrorRange && firstLimit.mode === 'distance') {
      setSecondLimit((prev) => ({
        ...prev,
        mode: 'distance',
        distance: firstLimit.distance,
      }));
    }
  }, [isMirrorRange, firstLimit.distance, firstLimit.mode]);

  const validate = () => {
    const validateSingleLimit = (limit: SingleLimit) => {
      if (limit.mode === 'distance') {
        return true;
      }

      return limit.mode === 'toElement' && limit.element != null;
    };

    const isDistanceZero =
      firstLimit.mode === 'distance' &&
      secondLimit.mode === 'distance' &&
      firstLimit.distance * (firstLimit.reverse ? -1 : 1) +
        secondLimit.distance * (secondLimit.reverse ? -1 : 1) ===
        0;

    return (
      pickedProfile.length > 0 &&
      pickedVector != null &&
      validateSingleLimit(firstLimit) &&
      validateSingleLimit(secondLimit) &&
      !isDistanceZero
    );
  };

  const finishValid = useMemo(validate, [params]);

  const arrows = useRef<PickLine[]>([]);

  const execute = async (isPreview: boolean) => {
    if (validate()) {
      try {
        await confirmCommand(
          {
            type: 'Extrude',
            a: params,
          },
          isPreview,
        );
      } catch (e) {
        previewer.clear();
      }

      if (!isPreview) {
        return;
      }

      const vertexes: Vector3[] = pickedProfile[0].assisObj.getVertexs();
      let midPoint: Vector3;
      if (vertexes.length % 2 === 1) {
        midPoint = vertexes[(vertexes.length + 1) / 2];
      } else {
        const points = vertexes.slice(
          vertexes.length / 2 - 1,
          vertexes.length / 2 + 1,
        );

        midPoint = new Vector3(
          (points[0].x + points[1].x) / 2,
          (points[0].y + points[1].y) / 2,
          (points[0].z + points[1].z) / 2,
        );
      }

      let vector: Vector3;

      if ((pickedVector as pickedLineData).edgeId != null) {
        const vertexes: Vector3[] = (
          pickedVector as pickedLineData
        ).assisObj.getVertexs();

        vector = new Vector3()
          .subVectors(vertexes[vertexes.length - 1], vertexes[0])
          .normalize();
      }

      if ((pickedVector as pickedFaceData).meshId != null) {
        vector = getFaceNormal(pickedVector.idInfo);
      }

      arrows.current.forEach((a) => a.destroy());
      arrows.current = [];

      if (vector != null) {
        const appendArrow = (offset: number) => {
          if (offset === 0) {
            return;
          }
          const arrow = new PickLine({
            vertexs: [
              new Vector3(),
              vector.clone().setLength(offset > 0 ? 2 : -2),
            ],
            showArrow: true,
            isKeepSize: true,
            keepSizeOrigin: midPoint
              .clone()
              .add(vector.setLength(offset))
              .toArray(),
            camera: ctx2.viewer.sceneSetup.oCamera,
            lineWidth: 4,
          });

          arrows.current.push(arrow);
        };

        if (firstLimit.mode === 'distance') {
          appendArrow(firstLimit.distance * (firstLimit.reverse ? -1 : 1));
        }

        if (firstLimit.mode === 'distance') {
          appendArrow(secondLimit.distance * (secondLimit.reverse ? 1 : -1));
        }

        arrows.current.forEach((arrow) =>
          arrow.addTo(ctx2.cadScene.assisGroup),
        );
      }

      ctx2.viewer.requestRender();
    } else {
      arrows.current.forEach((a) => a.destroy());
      previewer.clear();
    }
  };

  useAsyncEffect(async () => {
    if (!finishValid) {
      arrows.current.forEach((a) => a.destroy());
      previewer.clear();
      return;
    }

    await execute(true);
  }, [params]);

  useAsyncEffect(async () => {
    if (props == null) {
      return;
    }

    const {
      profile,
      vector,
      mode,
      distance,
      reverse,
      element,
      mode2,
      distance2,
      reverse2,
      element2,
    } = props.params as Params;

    await initEditDependency([...profile, vector, element, element2]);

    const getLimit = (
      mode: LimitMode,
      distance: number,
      reverse: boolean,
      element: string,
    ) => {
      return {
        mode,
        distance,
        reverse,
        element:
          element &&
          (picker.generatePickObjByPickID(element) as
            | pickedFaceData
            | pickedVertexData),
      } as SingleLimit;
    };

    setPickedProfile(
      (profile ?? []).map(
        (dxid) => picker.generatePickObjByPickID(dxid) as pickedLineData,
      ),
    );

    setPickedVector(
      picker.generatePickObjByPickID(vector) as pickedFaceData | pickedLineData,
    );

    setFirstLimit(getLimit(mode, distance, reverse, element));
    setSecondLimit(getLimit(mode2, distance2, reverse2, element2));
  }, []);

  const finishAction = async () => {
    await execute(false);
    cancelAction();
  };

  const cancelAction = () => {
    previewer.clear();
    arrows.current.forEach((a) => a.destroy());
    onClose();
  };

  const { initEditDependency, initingEdit, confirmCommand } = useCommand(
    'cmd-extrude-surface',
    {
      commandName: 'CreateSurface',
      editDxid: props?.shape,
      onClose: cancelAction,
      finishDraw: finishAction,
    },
  );

  const renderLimit = (
    onChange: (newVal: SingleLimit) => void,
    val: SingleLimit,
    title: string,
    disableDistance?: boolean,
  ) => {
    return (
      <>
        <div className='text-sm'>{title}</div>
        <div className='flex'>
          <Select
            className='flex-1'
            value={val.mode}
            options={[
              {
                label: '尺寸',
                value: 'distance',
              },
              {
                label: '直到元素',
                value: 'toElement',
              },
            ]}
            onChange={(v) => {
              if (v === val.mode) {
                return;
              }
              onChange({
                reverse: val.reverse,
                mode: v,
                distance: v === 'distance' ? 20 : null,
                offset: v === 'distance' ? null : 0,
              });

              if (v !== 'distance') {
                setIsMirrorRange(false);
              }

              previewer.clear();
              arrows.current.forEach((a) => a.destroy());
            }}
          />
          <Icon
            className='ml-2 cursor-pointer'
            name={
              val.reverse
                ? 'commands/common/flip-direction-2'
                : 'commands/common/flip-direction-1'
            }
            onClick={() => {
              onChange({ ...val, reverse: !val.reverse });
            }}
          />
        </div>
        {val.mode === 'distance' && (
          <InputWithFormat
            disabled={disableDistance}
            className='w-full'
            value={val.distance}
            inputType={ParameterObjectType.Length}
            onChange={(v) => onChange({ ...val, distance: v as number })}
          />
        )}
        {val.mode === 'toElement' && (
          <PickInputForFocus
            allowPickType={[
              PickInputValueType.Plane,
              PickInputValueType.Vertex,
            ]}
            onPick={(v) => {
              onChange({ ...val, element: v[0] });
            }}
            value={[val.element]}
            placeholder='请选择元素'
            disabled={initingEdit}
          />
        )}
        {val.mode === 'toElement' && (
          <InputWithFormat
            className='w-full'
            value={val.offset}
            inputType={ParameterObjectType.Length}
            onChange={(v) => onChange({ ...val, offset: v as number })}
          />
        )}
      </>
    );
  };

  return (
    <CommandDialog
      title={title}
      onClose={onClose}
      placement='topLeft'
    >
      <CommandFormWrapper>
        <PickInputForFocus
          allowPickType={[PickInputValueType.Curve, PickInputValueType.Sketch]}
          allowInfinitePlane={false}
          onPick={(v) => {
            setPickedProfile(v);
          }}
          value={pickedProfile}
          placeholder='请输入轮廓'
          disabled={initingEdit}
          autoFocus
        />
        <PickInputForFocus
          allowPickType={[PickInputValueType.Plane, PickInputValueType.Line]}
          onPick={(v) => {
            setPickedVector(v[0]);
          }}
          value={pickedVector == null ? [] : [pickedVector]}
          placeholder='请输入方向'
          disabled={initingEdit}
        />
        <Checkbox
          checked={isMirrorRange}
          onChange={(e) => setIsMirrorRange(e.target.checked)}
          disabled={
            firstLimit.mode !== 'distance' || secondLimit.mode !== 'distance'
          }
        >
          镜像范围
        </Checkbox>
        {renderLimit(setFirstLimit, firstLimit, '限制1')}
        {renderLimit(setSecondLimit, secondLimit, '限制2', isMirrorRange)}
      </CommandFormWrapper>
      <CommandFormFooter
        onSubmit={finishAction}
        onClose={onClose}
        isSubmitDisabled={!finishValid}
      />
    </CommandDialog>
  );
}

export const cmdExtrudeSurface: CurvedBaseCommand = {
  id: 'CmdExtrudeSurfance',
  label: 'command.extrude-surface',
  icon: 'commands/curved/extrude-surface',
  help: {
    remark: 'tips.redo',
  },
  create: (label, onClose, props) => (
    <CmdExtrudeSurface
      title={i18n.t(label)}
      onClose={onClose}
      props={props}
    />
  ),
};
